//*****************************************************************************
// TITLE:         POINT CLOUD  
// DESCRIPTION:   CLOUD class for GSB to manipulate and dislpay points
// AUTHOR:        Prof Jarek Rossignac
// DATE CREATED:  September 2009
// EDITS:
//*****************************************************************************
class CLOUD {                     
  int kmax=100;
  int n=4;                            // current number of control points
  pt [] P = new pt[kmax];            // decalres an array of  points
  float [] w = new float[kmax];            // decalres an array of  points
  Boolean [] c = new Boolean[kmax];            // weight change
  int p=0;                          // index to the currently selected vertex being dragged
  CLOUD() {
    declarePoints(); 
    resetPoints();
  }
  CLOUD(int pn) {
    n=pn; 
    declarePoints(); 
    resetPoints();
  }

  // ************************************** CREATE POINTS *********************************
  void declarePoints() {
    for (int i=0; i<P.length; i++) {
      P[i]=new pt(); 
      c[i]=false;
    }
  } // init the vertices to be on a circle
  void resetPoints() {
    for (int i=0; i<n; i++) {
      P[i]=new pt(width/2, height/10.); 
      P[i].rotate(-2.*PI*i/n, ScreenCenter());
    };
  } // init the points to be on a circle
  void resetPointsOnCircle() {
    for (int i=0; i<n; i++) {
      P[i].x=height/2; 
      P[i].y=height/10; 
      P[i].rotate(-2.*PI*i/n, ScreenCenter()); 
      w[i]= -2.*PI*i/n;
    };
  } // init the points to be on a circle
  void resetPointsOnLine() {
    for (int i=0; i<n; i++) {
      P[i].x=height*(i+1)/(n+1); 
      P[i].y=height/2; 
      w[i]=0;
    };
  } // init the points to be on a circle
  void appendPoint(pt Q) { 
    w[n]=w[n-1]; 
    P[n++].setTo(Q);
  }; // add point at end of list
  void insertPoint(pt Q) {
    for (int i=n-1; i>p; i--) P[i+1].setTo(P[i]); 
    n++; 
    p++; 
    P[p].setTo(Q);
  };
  void deletePoint() { 
    for (int i=p; i<n-1; i++) P[i].setTo(P[i+1]); 
    n--; 
    p=max(0, p-1);
  }
  void empty() { 
    n=0;
  };      // resets the vertex count to zero
  void perturb(float e) {
    for (int i=0; i<n; i++) { 
      P[i].x+=random(e); 
      P[i].y+=random(e);
    } 
    ;
  }

  // ************************************** IMPORT POINTS FORM ANOTHER CLOUD *********************************
  void clone(CLOUD D) {
    for (int i=0; i<max(n,D.n); i++) {
      P[i].setTo(D.P[i]); 
      w[i]=D.w[i];
    } 
    n=D.n;
  }
  void extractFrom(pt [] Q, int start, int end ) {
    n=end-start+1; 
    for (int i=0; i<n; i++) P[i].setTo(Q[i+start]);
  };  // makes P be the pvn first points of Q

  // ************************************** SELECT AND TRANSFORM POINTS *********************************
  pt pt() {
    return P[p];
  }
  float orientation() {
    return w[p];
  }
  pt pt(int i) {
    return P[i];
  }
  float orientation(int i) {
    return w[i];
  }
  void pickClosestPoint(pt M) {
    p=0; 
    for (int i=1; i<n; i++) if (d(M, P[i])<d(M, P[p])) p=i;
  }
  void dragPoint() { 
    P[p].moveWithMouse();
  }      // moves selected point (index p) by amount mouse moved recently
  void dragPoint(vec V) {
    P[p].add(V);
  }
  void translatePoints(vec V) {
    for (int i=0; i<n; i++) P[i].add(V);
  };   
  void scalePointsRelative(float s, pt G) {
    for (int i=0; i<n; i++) P[i]=I(G, s, P[i]);
  };  
  void scalePoints(float s, pt G) {
    for (int i=0; i<n; i++) P[i].translateTowards(s, G);
  };  
  void scalePoints(float s, pt G, vec V) {
    for (int i=0; i<n; i++) P[i].translateTowards(s, G);
  };  
  void scalePoints(float s) {
    scalePoints(s, verticesCenter());
  };
  void scalePointsAroundCenterOfMass(pt M, vec V) {
    pt C=verticesCenter(); 
    scalePoints(dot(V, U(M, C))/d(M, C), C);
  };
  void rotatePoints(float a, pt G) {
    for (int i=0; i<n; i++) {
      P[i].rotate(a, G); 
      w[i]-=a;
    }
  }; // rotates points around pt G by angle a
  void rotatePointsAroundCenterOfMass(float a) {
    rotatePoints(a, verticesCenter());
  }; // rotates points around their center of mass by angle a
  void rotatePoints(pt G, pt P, pt Q) {
    rotatePoints(angle(V(G, P), V(G, Q)), verticesCenter());
  }; // rotates points around G by angle <GP,GQ>
  void rotateOrientation() {
    w[p]-=angle(V(P[p], Mouse()), V(P[p], Pmouse()));
  }; // rotates points around G by angle <GP,GQ>
  void weightChange() {
    c[p]=!c[p];

  }; // rotates points around G by angle <GP,GQ>
  void rotatePointsAroundCenterOfMass(pt P, pt Q) {
    rotatePoints(verticesCenter(), P, Q);
  }; // rotates points around their center of mass G by angle <GP,GQ>
  void frame() {
    float sx=height; 
    float sy=height; 
    float bx=0.0; 
    float by=0.0; 
    for (int i=0; i<n; i++) {
      if (P[i].x>bx) {
        bx=P[i].x;
      }; 
      if (P[i].x<sx) {
        sx=P[i].x;
      }; 
      if (P[i].y>by) {
        by=P[i].y;
      }; 
      if (P[i].y<sy) {
        sy=P[i].y;
      };
    };
    float m=max(bx-sx, by-sy);  
    float dx=(m-(bx-sx))/2; 
    float dy=(m-(by-sy))/2; 
    for (int i=0; i<n; i++) {
      P[i].x=(P[i].x-sx+dx)*4*height/5/m+height/10;  
      P[i].y=(P[i].y-sy+dy)*4*height/5/m+height/10;
    };
  }   

  // ************************************** REGISTER *********************************
  void registerTo(CLOUD Q) {  // vertex registration
    pt A=verticesCenter(); 
    pt B=Q.verticesCenter(); 
    float s=0; 
    for (int i=0; i<min(n,Q.n); i++) s+=dot(V(A, P[i]), R(V(B, Q.P[i])));
    float c=0; 
    for (int i=0; i<min(n,Q.n); i++) c+=dot(V(A, P[i]), V(B, Q.P[i]));
    float a = atan2(s, c);
    translatePoints(V(A, B));
    rotatePoints(a, B);
  } 

  // ************************************** VIEW *********************************
  pt first() {
    return P[0];
  }  // returns first point
  pt last() {
    return P[n-1];
  }  // returns last point
  pt picked() {
    return P[p];
  }  // returns picked point
  void drawArrowsTo(CLOUD R) {
    for (int i=0; i<min(n,R.n); i++) arrow(P[i], R.P[i]);
  };
  void drawCorrespondenceTo(CLOUD R) {
    for (int i=0; i<min(n,R.n); i++) show(P[i], R.P[i]);
  };
  void drawPoints() {
    for (int i=0; i<n; i++) P[i].show();
  }
  void drawPointsArrows(int r) {
    vec V=V(30, 0); 
    for (int i=0; i<n; i++) {
      P[i].show(r); 
      arrow(P[i], R(V, w[i]));
    }
  }
  void drawPoints(int r) { 
    for (int i=0; i<n; i++) {
      P[i].show(r);
    }
  }
  void drawDots() {
    beginShape(POINTS); 
    for (int i=0; i<n; i++) v(P[i]); 
    endShape();
  }  // fast draw of points
  void writePointIDs() {
    for (int i=0; i<n; i++) label(P[i], S(15, U(verticesCenter(), P[i])), str(i));
  }; 
  void writePointLetters() {
    for (int i=0; i<n; i++) label(P[i], str(char(i+65)));
  }; 

  // ************************************** MEASURE *********************************
  pt verticesCenter() {
    pt G=P(); 
    for (int i=0; i<n; i++) G.add(P[i]); 
    return S(1./n, G);
  } 
  pt ClosestVertex(pt M) {
    pt R=P[0]; 
    for (int i=1; i<n; i++) if (d(M, P[i])<d(M, R)) R=P[i]; 
    return P(R);
  }
  float distanceTo(pt M) {
    return d(M, ClosestVertex(M));
  }

  // ************************************** SAVE TO FILE AND READ BACK *********************************
  void savePts() {
    savePts("data/P.pts");
  }
  void savePts(String fn) { 
    String [] inppts = new String [n+1];
    int s=0; 
    inppts[s++]=str(n); 
    for (int i=0; i<n; i++) {
      if (c[i]) inppts[s++]=str(P[i].x)+","+str(P[i].y)+","+str(w[i])+",C";
      else inppts[s++]=str(P[i].x)+","+str(P[i].y)+","+str(w[i])+", ";
    };
    saveStrings(fn, inppts);
  };
  void loadPts() {
    loadPts("data/P.pts");
  }
  void loadPts(String fn) { 
    String [] ss = loadStrings(fn);
    String subpts;
    int s=0; 
    int comma; 
    n = int(ss[s]);
    for (int i=0;i<n; i++) { 
      String SS=ss[++s];
      comma=SS.indexOf(',');
      P[i].x=float(ss[s].substring(0, comma));
      SS=SS.substring(comma+1, SS.length());
      comma=SS.indexOf(',');
      P[i].y=float(SS.substring(0, comma));
      SS=SS.substring(comma+1, SS.length());
      comma=SS.indexOf(',');
      w[i]=float(SS.substring(0, comma)); 
      c[i]=SS.indexOf('C')==comma+1;
    };
  };
} // end CLOUD

